/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2016 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.platform.plugin; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.platform.api.engine.IAcceptsRuntimeInputs; import org.pentaho.platform.api.engine.IActionSequenceResource; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.IPluginManager; import org.pentaho.platform.api.engine.IStreamingPojo; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.reporting.engine.classic.core.AttributeNames; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.ReportInterruptedException; import org.pentaho.reporting.engine.classic.core.metadata.ReportProcessTaskRegistry; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfPageableModule; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.plaintext.PlainTextPageableModule; import org.pentaho.reporting.engine.classic.core.modules.output.pageable.xml.XmlPageableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVTableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlTableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.rtf.RTFTableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelTableModule; import org.pentaho.reporting.engine.classic.core.modules.output.table.xml.XmlTableModule; import org.pentaho.reporting.engine.classic.core.parameters.DefaultParameterContext; import org.pentaho.reporting.engine.classic.core.parameters.ParameterContext; import org.pentaho.reporting.engine.classic.core.parameters.ValidationResult; import org.pentaho.reporting.engine.classic.extensions.modules.java14print.Java14PrintUtil; import org.pentaho.reporting.libraries.base.util.CSVQuoter; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.resourceloader.ResourceException; import org.pentaho.reporting.libraries.xmlns.common.ParserUtil; import org.pentaho.reporting.platform.plugin.cache.NullReportCache; import org.pentaho.reporting.platform.plugin.cache.ReportCache; import org.pentaho.reporting.platform.plugin.cache.ReportCacheKey; import org.pentaho.reporting.platform.plugin.messages.Messages; import org.pentaho.reporting.platform.plugin.output.FastExportReportOutputHandlerFactory; import org.pentaho.reporting.platform.plugin.output.ReportOutputHandler; import org.pentaho.reporting.platform.plugin.output.ReportOutputHandlerFactory; import org.pentaho.reporting.platform.plugin.output.ReportOutputHandlerSelector; import javax.print.DocFlavor; import javax.print.PrintService; import javax.print.PrintServiceLookup; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.Map; public class SimpleReportingComponent implements IStreamingPojo, IAcceptsRuntimeInputs { /** * The logging for logging messages from this component */ private static final Log log = LogFactory.getLog( SimpleReportingComponent.class ); public static final String OUTPUT_TARGET = "output-target"; //$NON-NLS-1$ public static final String OUTPUT_TYPE = "output-type"; //$NON-NLS-1$ public static final String MIME_TYPE_HTML = "text/html"; //$NON-NLS-1$ public static final String MIME_TYPE_EMAIL = "mime-message/text/html"; //$NON-NLS-1$ public static final String MIME_TYPE_PDF = "application/pdf"; //$NON-NLS-1$ public static final String MIME_TYPE_XLS = "application/vnd.ms-excel"; //$NON-NLS-1$ public static final String MIME_TYPE_XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; //$NON-NLS-1$ public static final String MIME_TYPE_RTF = "application/rtf"; //$NON-NLS-1$ public static final String MIME_TYPE_CSV = "text/csv"; //$NON-NLS-1$ public static final String MIME_TYPE_TXT = "text/plain"; //$NON-NLS-1$ public static final String MIME_TYPE_XML = "application/xml"; //$NON-NLS-1$ public static final String MIME_TYPE_PNG = "image/png"; //$NON-NLS-1$ public static final String XLS_WORKBOOK_PARAM = "workbook"; //$NON-NLS-1$ public static final String REPORTLOAD_RESURL = "res-url"; //$NON-NLS-1$ public static final String REPORT_DEFINITION_INPUT = "report-definition"; //$NON-NLS-1$ public static final String REPORTHTML_CONTENTHANDLER_PATTERN = "content-handler-pattern"; //$NON-NLS-1$ public static final String REPORTGENERATE_YIELDRATE = "yield-rate"; //$NON-NLS-1$ public static final String ACCEPTED_PAGE = "accepted-page"; //$NON-NLS-1$ public static final String PAGINATE_OUTPUT = "paginate"; //$NON-NLS-1$ public static final String PRINT = "print"; //$NON-NLS-1$ public static final String PRINTER_NAME = "printer-name"; //$NON-NLS-1$ public static final String DASHBOARD_MODE = "dashboard-mode"; //$NON-NLS-1$ private static final String MIME_GENERIC_FALLBACK = "application/octet-stream"; //$NON-NLS-1$ public static final String PNG_EXPORT_TYPE = "pageable/X-AWT-Graphics;image-type=png"; /** * Static initializer block to guarantee that the ReportingComponent will be in a state where the reporting engine * will be booted. We have a system listener which will boot the reporting engine as well, but we do not want to * solely rely on users having this setup correctly. The errors you receive if the engine is not booted are not very * helpful, especially to outsiders, so we are trying to provide multiple paths to success. Enjoy. */ static { final ReportingSystemStartupListener startup = new ReportingSystemStartupListener(); startup.startup( null ); } /** * The output-type for the generated report, such as PDF, XLS, CSV, HTML, etc This must be the mime-type! */ private String outputType; private String outputTarget; private String defaultOutputTarget; private boolean forceDefaultOutputTarget; private boolean forceUnlockPreferredOutput = false; private MasterReport report; private Map<String, Object> inputs; private OutputStream outputStream; private InputStream reportDefinitionInputStream; private IActionSequenceResource reportDefinition; private Serializable fileId; private String reportDefinitionPath; private boolean paginateOutput; private int acceptedPage; private int pageCount; private boolean dashboardMode; /* * These fields are for enabling printing */ private boolean print = false; private String printer; /* * Default constructor */ public SimpleReportingComponent() { this.inputs = Collections.emptyMap(); acceptedPage = -1; pageCount = -1; defaultOutputTarget = HtmlTableModule.TABLE_HTML_STREAM_EXPORT_TYPE; } // ---------------------------------------------------------------------------- // BEGIN BEAN METHODS // ---------------------------------------------------------------------------- public String getDefaultOutputTarget() { return defaultOutputTarget; } public void setDefaultOutputTarget( final String defaultOutputTarget ) { if ( defaultOutputTarget == null ) { throw new NullPointerException(); } this.defaultOutputTarget = defaultOutputTarget; } public void setForceDefaultOutputTarget( final boolean forceDefaultOutputTarget ) { this.forceDefaultOutputTarget = forceDefaultOutputTarget; } public boolean isForceDefaultOutputTarget() { return this.forceDefaultOutputTarget; } public void setForceUnlockPreferredOutput( boolean forceUnlockPreferredOutput ) { this.forceUnlockPreferredOutput = forceUnlockPreferredOutput; } public boolean isForceUnlockPreferredOutput() { return forceUnlockPreferredOutput; } public String getOutputTarget() { return outputTarget; } public void setOutputTarget( final String outputTarget ) { this.outputTarget = outputTarget; } /** * Sets the mime-type for determining which report output type to generate. This should be a mime-type for consistency * with streaming output mime-types. * * @param outputType the desired output type (mime-type) for the report engine to generate */ public void setOutputType( final String outputType ) { this.outputType = outputType; } /** * Gets the output type, this should be a mime-type for consistency with streaming output mime-types. * * @return the current output type for the report */ public String getOutputType() { return outputType; } /** * This method returns the resource for the report-definition, if available. * * @return the report-definition resource */ public IActionSequenceResource getReportDefinition() { return reportDefinition; } /** * Sets the report-definition if it is provided to us by way of an action-sequence resource. The name must be * reportDefinition or report-definition. * * @param reportDefinition a report-definition as seen (wrapped) by an action-sequence */ public void setReportDefinition( final IActionSequenceResource reportDefinition ) { this.reportDefinition = reportDefinition; this.report = null; } /** * This method will be called if an input is called reportDefinitionInputStream, or any variant of that with dashes * report-definition-inputstream for example. The primary purpose of this method is to facilitate unit testing. * * @param reportDefinitionInputStream any kind of InputStream which contains a valid report-definition */ public void setReportDefinitionInputStream( final InputStream reportDefinitionInputStream ) { this.reportDefinitionInputStream = reportDefinitionInputStream; this.report = null; } /** * Returns the path to the report definition (for platform use this is a path in the solution repository) * * @return reportdefinitionPath */ public Serializable getReportFileId() { return fileId; } /** * Sets the path to the report definition (platform path) * * @param fileId the path to the report definition. */ public void setReportFileId( final Serializable fileId ) { this.fileId = fileId; } /** * Returns the path to the report definition (for platform use this is a path in the solution repository) * * @return reportdefinitionPath */ public String getReportDefinitionPath() { return reportDefinitionPath; } /** * Sets the path to the report definition (platform path) * * @param reportDefinitionPath the path to the report definition. */ public void setReportDefinitionPath( String reportDefinitionPath ) { this.reportDefinitionPath = reportDefinitionPath; } /** * Returns true if the report engine will be asked to use a paginated (HTML) output processor * * @return paginated */ public boolean isPaginateOutput() { return paginateOutput; } /** * Set the paging mode used by the reporting engine. This will also be set if an input * * @param paginateOutput page mode */ public void setPaginateOutput( final boolean paginateOutput ) { this.paginateOutput = paginateOutput; } public int getAcceptedPage() { return acceptedPage; } public void setAcceptedPage( final int acceptedPage ) { this.acceptedPage = acceptedPage; } /** * This method sets the IPentahoSession to use in order to access the pentaho platform file repository and content * repository. * * @param session a valid pentaho session * @deprecated No longer used. */ public void setSession( final IPentahoSession session ) { } public boolean isDashboardMode() { return dashboardMode; } public void setDashboardMode( final boolean dashboardMode ) { this.dashboardMode = dashboardMode; } /** * This method returns the mime-type for the streaming output based on the effective output target. * * @return the mime-type for the streaming output * @see SimpleReportingComponent#computeEffectiveOutputTarget() */ public String getMimeType() { try { final String outputTarget = computeEffectiveOutputTarget(); if ( log.isDebugEnabled() ) { log.debug( Messages.getInstance().getString( "ReportPlugin.logComputedOutputTarget", outputTarget ) ); } ReportOutputHandlerFactory handlerFactory = PentahoSystem.get( ReportOutputHandlerFactory.class ); if ( handlerFactory == null ) { handlerFactory = new FastExportReportOutputHandlerFactory(); } return handlerFactory.getMimeType( new InternalOutputHandlerSelector( outputTarget ) ); } catch ( IOException e ) { if ( log.isDebugEnabled() ) { log.warn( Messages.getInstance().getString( "ReportPlugin.logErrorMimeTypeFull" ), e ); } else if ( log.isWarnEnabled() ) { log.warn( Messages.getInstance().getString( "ReportPlugin.logErrorMimeTypeShort", e.getMessage() ) ); } return MIME_GENERIC_FALLBACK; } catch ( ResourceException e ) { if ( log.isDebugEnabled() ) { log.warn( Messages.getInstance().getString( "ReportPlugin.logErrorMimeTypeFull" ), e ); } else if ( log.isWarnEnabled() ) { log.warn( Messages.getInstance().getString( "ReportPlugin.logErrorMimeTypeShort", e.getMessage() ) ); } return MIME_GENERIC_FALLBACK; } } /** * This method sets the OutputStream to write streaming content on. * * @param outputStream an OutputStream to write to */ public void setOutputStream( final OutputStream outputStream ) { this.outputStream = outputStream; } /** * This method checks if the output is targeting a printer * * @return true if the output is supposed to go to a printer */ public boolean isPrint() { return print; } /** * Set whether or not to send the report to a printer * * @param print a flag indicating whether the report should be printed. */ public void setPrint( final boolean print ) { this.print = print; } /** * This method gets the name of the printer the report will be sent to * * @return the name of the printer that the report will be sent to */ public String getPrinter() { return printer; } /** * Set the name of the printer to send the report to * * @param printer the name of the printer that the report will be sent to, a null value will be interpreted as the * default printer */ public void setPrinter( final String printer ) { this.printer = printer; } /** * Get the inputs, needed by subclasses, such as with interactive adhoc * * @return immutable input map */ public Map<String, Object> getInputs() { if ( inputs != null ) { return Collections.unmodifiableMap( inputs ); } return Collections.emptyMap(); } /** * This method sets the map of *all* the inputs which are available to this component. This allows us to use * action-sequence inputs as parameters for our reports. * * @param inputs a Map containing inputs */ public void setInputs( final Map<String, Object> inputs ) { if ( inputs == null ) { this.inputs = Collections.emptyMap(); return; } this.inputs = inputs; if ( inputs.containsKey( OUTPUT_TYPE ) ) { setOutputType( String.valueOf( inputs.get( OUTPUT_TYPE ) ) ); } if ( inputs.containsKey( OUTPUT_TARGET ) ) { setOutputTarget( String.valueOf( inputs.get( OUTPUT_TARGET ) ) ); } if ( inputs.containsKey( REPORT_DEFINITION_INPUT ) ) { setReportDefinitionInputStream( (InputStream) inputs.get( REPORT_DEFINITION_INPUT ) ); } if ( inputs.containsKey( PAGINATE_OUTPUT ) ) { paginateOutput = "true".equals( String.valueOf( inputs.get( PAGINATE_OUTPUT ) ) ); //$NON-NLS-1$ } if ( inputs.containsKey( ACCEPTED_PAGE ) ) { acceptedPage = ParserUtil.parseInt( String.valueOf( inputs.get( ACCEPTED_PAGE ) ), -1 ); //$NON-NLS-1$ } if ( inputs.containsKey( PRINT ) ) { print = "true".equals( String.valueOf( inputs.get( PRINT ) ) ); //$NON-NLS-1$ } if ( inputs.containsKey( PRINTER_NAME ) ) { printer = String.valueOf( inputs.get( PRINTER_NAME ) ); } if ( inputs.containsKey( DASHBOARD_MODE ) ) { dashboardMode = "true".equals( String.valueOf( inputs.get( DASHBOARD_MODE ) ) ); //$NON-NLS-1$ } if ( inputs.containsKey( ParameterXmlContentHandler.SYS_PARAM_QUERY_LIMIT ) ) { try { if ( inputs.get( ParameterXmlContentHandler.SYS_PARAM_QUERY_LIMIT ) != null ) { this.getReport().setQueryLimit( checkAndGetUserInputQueryLimit( inputs.get( ParameterXmlContentHandler.SYS_PARAM_QUERY_LIMIT ), this.getReport().getQueryLimit() ) ); } } catch ( final Exception e ) { log.warn( e.getMessage(), e ); } } } // ---------------------------------------------------------------------------- // END BEAN METHODS // ---------------------------------------------------------------------------- protected Object getInput( final String key, final Object defaultValue ) { if ( inputs != null ) { final Object input = inputs.get( key ); if ( input != null ) { return input; } } return defaultValue; } /** * Sets the MasterReport for the report-definition, needed by subclasses, such as with interactive adhoc * * @return nothing */ public void setReport( MasterReport report ) { this.report = report; final String clText = extractContentLinkSpec(); report.setReportEnvironment( new PentahoReportEnvironment( report.getConfiguration(), clText ) ); } /** * Get the MasterReport for the report-definition, the MasterReport object will be cached as needed, using the * PentahoResourceLoader. * * @return a parsed MasterReport object * @throws ResourceException * @throws IOException */ public MasterReport getReport() throws ResourceException, IOException { if ( report == null ) { if ( reportDefinitionInputStream != null ) { report = ReportCreator.createReport( reportDefinitionInputStream, getDefinedResourceURL( null ) ); } else if ( reportDefinition != null ) { // load the report definition as an action-sequence resource report = ReportCreator.createReportByName( reportDefinition.getAddress() ); } else if ( reportDefinitionPath != null ) { // load the report definition as an action-sequence resource report = ReportCreator.createReportByName( reportDefinitionPath ); } else if ( fileId != null ) { report = ReportCreator.createReport( fileId ); } else { throw new ResourceException(); } final String clText = extractContentLinkSpec(); report.setReportEnvironment( new PentahoReportEnvironment( report.getConfiguration(), clText ) ); } try { // force autoSubmit flag (based on settings.xml)? IPluginManager pm = PentahoSystem.get( IPluginManager.class ); if ( pm != null ) { Object autoSubmitSetting = pm.getPluginSetting( "reporting", "settings/auto-submit", null ); if ( autoSubmitSetting != null ) { boolean autoSubmit = Boolean.parseBoolean( autoSubmitSetting.toString() ); report.setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.AUTO_SUBMIT_PARAMETER, autoSubmit ); } Object autoSubmitDefaultSetting = pm.getPluginSetting( "reporting", "settings/auto-submit-default", null ); if ( autoSubmitDefaultSetting != null ) { boolean autoSubmitDefault = Boolean.parseBoolean( autoSubmitDefaultSetting.toString() ); report.setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.AUTO_SUBMIT_DEFAULT, autoSubmitDefault ); } // lock preferred output? if ( forceUnlockPreferredOutput && Boolean.parseBoolean( pm.getPluginSetting( "reporting", "settings/force-prpti-output-unlock", "false" ) .toString() ) ) { report.setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.LOCK_PREFERRED_OUTPUT_TYPE, false ); } } } catch ( Throwable t ) { log.warn( t.getMessage(), t ); } return report; } private String extractContentLinkSpec() { if ( inputs == null ) { return null; } final Object clRaw = inputs.get( ParameterXmlContentHandler.SYS_PARAM_CONTENT_LINK ); if ( clRaw == null ) { return null; } if ( clRaw instanceof Collection ) { final Collection<?> c = (Collection<?>) clRaw; final CSVQuoter quoter = new CSVQuoter( ',', '"' ); final StringBuilder b = new StringBuilder(); for ( final Object o : c ) { final String s = quoter.doQuoting( String.valueOf( o ) ); if ( b.length() > 0 ) { b.append( ',' ); } b.append( s ); } return b.toString(); } if ( clRaw.getClass().isArray() ) { final CSVQuoter quoter = new CSVQuoter( ',', '"' ); final StringBuilder b = new StringBuilder(); for ( int i = 0, size = Array.getLength( clRaw ); i < size; i++ ) { final Object o = Array.get( clRaw, i ); final String s = quoter.doQuoting( String.valueOf( o ) ); if ( b.length() > 0 ) { b.append( ',' ); } b.append( s ); } return b.toString(); } else { return String.valueOf( clRaw ); } } private boolean isValidOutputType( final String outputType ) { if ( PNG_EXPORT_TYPE.equals( outputType ) ) { return true; } return ReportProcessTaskRegistry.getInstance().isExportTypeRegistered( outputType ); } /** * Computes the effective output target that will be used when running the report. This method does not modify any of * the properties of this class. * <p/> * The algorithm to determine the output target is as follows: <ul> <li> If the report attribute * "lock-preferred-output-type" is set, and the attribute preferred-output-type is set, the report will always be * exported to the specified output type.</li> <li>If the component has the parameter "output-target" set, this output * target will be used.</li> <li>If the component has the parameter "output-type" set, the mime-type will be * translated into a suitable output target (depends on other parameters like paginate as well.</li> <li>If neither * output-target or output-type are specified, the report's preferred output type will be used.</li> <li>If no * preferred output type is set, we default to HTML export.</li> </ul> * <p/> * If the output type given is invalid, the report will not be executed and calls to * <code>SimpleReportingComponent#getMimeType()</code> will yield the generic "application/octet-stream" response. * * @return * @throws IOException * @throws ResourceException */ private String computeEffectiveOutputTarget() throws IOException, ResourceException { final MasterReport report = getReport(); if ( isForceDefaultOutputTarget() ) { return getDefaultOutputTarget(); } if ( Boolean.TRUE.equals( report.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.LOCK_PREFERRED_OUTPUT_TYPE ) ) ) { // preferred output type is one of the engine's output-target identifiers. It is not a mime-type string. // The engine supports multiple subformats per mime-type (example HTML: zipped/streaming/flow/pageable) // The mime-mapping would be inaccurate. final Object preferredOutputType = report.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.PREFERRED_OUTPUT_TYPE ); if ( preferredOutputType != null ) { final String preferredOutputTypeString = String.valueOf( preferredOutputType ); if ( isValidOutputType( preferredOutputTypeString ) ) { // if it is a recognized process-type, then fine, return it. return preferredOutputTypeString; } final String mappedLegacyType = mapOutputTypeToOutputTarget( preferredOutputTypeString ); if ( mappedLegacyType != null ) { log.warn( Messages.getInstance().getString( "ReportPlugin.warnLegacyLockedOutput", preferredOutputTypeString ) ); return mappedLegacyType; } log.warn( Messages.getInstance().getString( "ReportPlugin.warnInvalidLockedOutput", preferredOutputTypeString ) ); } } final String outputTarget = getOutputTarget(); if ( outputTarget != null ) { if ( isValidOutputType( outputTarget ) == false ) { log.warn( Messages.getInstance().getString( "ReportPlugin.warnInvalidOutputTarget", outputTarget ) ); } // if a engine-level output target is given, use it as it is. We can assume that the user knows how to // map from that to a real mime-type. return outputTarget; } final String mappingFromParams = mapOutputTypeToOutputTarget( getOutputType() ); if ( mappingFromParams != null ) { return mappingFromParams; } // if nothing is specified explicity, we may as well ask the report what it prefers.. final Object preferredOutputType = report.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.PREFERRED_OUTPUT_TYPE ); if ( preferredOutputType != null ) { final String preferredOutputTypeString = String.valueOf( preferredOutputType ); if ( isValidOutputType( preferredOutputTypeString ) ) { return preferredOutputTypeString; } final String mappedLegacyType = mapOutputTypeToOutputTarget( preferredOutputTypeString ); if ( mappedLegacyType != null ) { log.warn( Messages.getInstance() .getString( "ReportPlugin.warnLegacyPreferredOutput", preferredOutputTypeString ) ); return mappedLegacyType; } log.warn( Messages.getInstance().getString( "ReportPlugin.warnInvalidPreferredOutput", preferredOutputTypeString, getDefaultOutputTarget() ) ); return getDefaultOutputTarget(); } if ( StringUtils.isEmpty( getOutputTarget() ) == false || StringUtils.isEmpty( getOutputType() ) == false ) { // if you have come that far, it means you really messed up. Sorry, this error is not a error caused // by our legacy code - it is more likely that you just entered values that are totally wrong. log.error( Messages.getInstance().getString( "ReportPlugin.warnInvalidOutputType", getOutputType(), getDefaultOutputTarget() ) ); } return getDefaultOutputTarget(); } public String getComputedOutputTarget() throws IOException, ResourceException { return computeEffectiveOutputTarget(); } private String mapOutputTypeToOutputTarget( final String outputType ) { // if the user has given a mime-type instead of a output-target, lets map it to the "best" choice. If the // user wanted full control, he would have used the output-target property instead. if ( MIME_TYPE_CSV.equals( outputType ) ) { return CSVTableModule.TABLE_CSV_STREAM_EXPORT_TYPE; } if ( MIME_TYPE_HTML.equals( outputType ) ) { if ( isPaginateOutput() ) { return HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE; } return HtmlTableModule.TABLE_HTML_STREAM_EXPORT_TYPE; } if ( MIME_TYPE_XML.equals( outputType ) ) { if ( isPaginateOutput() ) { return XmlTableModule.TABLE_XML_EXPORT_TYPE; } return XmlPageableModule.PAGEABLE_XML_EXPORT_TYPE; } if ( MIME_TYPE_PDF.equals( outputType ) ) { return PdfPageableModule.PDF_EXPORT_TYPE; } if ( MIME_TYPE_RTF.equals( outputType ) ) { return RTFTableModule.TABLE_RTF_FLOW_EXPORT_TYPE; } if ( MIME_TYPE_XLS.equals( outputType ) ) { return ExcelTableModule.EXCEL_FLOW_EXPORT_TYPE; } if ( MIME_TYPE_XLSX.equals( outputType ) ) { return ExcelTableModule.XLSX_FLOW_EXPORT_TYPE; } if ( MIME_TYPE_EMAIL.equals( outputType ) ) { return MIME_TYPE_EMAIL; } if ( MIME_TYPE_TXT.equals( outputType ) ) { return PlainTextPageableModule.PLAINTEXT_EXPORT_TYPE; } if ( "pdf".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedPDF" ) ); return PdfPageableModule.PDF_EXPORT_TYPE; } else if ( "html".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedHTML" ) ); if ( isPaginateOutput() ) { return HtmlTableModule.TABLE_HTML_PAGE_EXPORT_TYPE; } return HtmlTableModule.TABLE_HTML_STREAM_EXPORT_TYPE; } else if ( "csv".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedCSV" ) ); return CSVTableModule.TABLE_CSV_STREAM_EXPORT_TYPE; } else if ( "rtf".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedRTF" ) ); return RTFTableModule.TABLE_RTF_FLOW_EXPORT_TYPE; } else if ( "xls".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedXLS" ) ); return ExcelTableModule.EXCEL_FLOW_EXPORT_TYPE; } else if ( "txt".equalsIgnoreCase( outputType ) ) { //$NON-NLS-1$ log.warn( Messages.getInstance().getString( "ReportPlugin.warnDeprecatedTXT" ) ); return PlainTextPageableModule.PLAINTEXT_EXPORT_TYPE; } return null; } /** * Apply inputs (if any) to corresponding report parameters, care is taken when checking parameter types to perform * any necessary casting and conversion. * * @param report a MasterReport object to apply parameters to * @param context a ParameterContext for which the parameters will be under * @deprecated use the single parameter version instead. This method will now fail with an error if the report passed * in is not the same as the report this component has. This method will be removed in version 4.0. */ public void applyInputsToReportParameters( final MasterReport report, final ParameterContext context ) { try { if ( getReport() != report ) { throw new IllegalStateException( Messages.getInstance().getString( "ReportPlugin.errorForeignReportInput" ) ); } final ValidationResult validationResult = applyInputsToReportParameters( context, null ); if ( validationResult.isEmpty() == false ) { throw new IllegalStateException( Messages.getInstance().getString( "ReportPlugin.errorApplyInputsFailed" ) ); } } catch ( IOException e ) { throw new IllegalStateException( Messages.getInstance().getString( "ReportPlugin.errorApplyInputsFailed" ), e ); } catch ( ResourceException e ) { throw new IllegalStateException( Messages.getInstance().getString( "ReportPlugin.errorApplyInputsFailed" ), e ); } } /** * Apply inputs (if any) to corresponding report parameters, care is taken when checking parameter types to perform * any necessary casting and conversion. * * @param context a ParameterContext for which the parameters will be under * @param validationResult the validation result that will hold the warnings. If null, a new one will be created. * @return the validation result containing any parameter validation errors. * @throws java.io.IOException if the report of this component could not be parsed. * @throws ResourceException if the report of this component could not be parsed. * @deprecated As of release 4.5, replaced by {@link ReportContentUtil#applyInputsToReportParameters(MasterReport, * ParameterContext, Map, ValidationResult)} */ @Deprecated public ValidationResult applyInputsToReportParameters( final ParameterContext context, ValidationResult validationResult ) throws IOException, ResourceException { return ReportContentUtil.applyInputsToReportParameters( getReport(), context, inputs, validationResult ); } private URL getDefinedResourceURL( final URL defaultValue ) { if ( inputs == null || inputs.containsKey( REPORTLOAD_RESURL ) == false ) { return defaultValue; } try { final String inputStringValue = (String) getInput( REPORTLOAD_RESURL, null ); return new URL( inputStringValue ); } catch ( Exception e ) { return defaultValue; } } protected int getYieldRate() { final Object yieldRate = getInput( REPORTGENERATE_YIELDRATE, null ); if ( yieldRate instanceof Number ) { final Number n = (Number) yieldRate; if ( n.intValue() < 1 ) { return 0; } return n.intValue(); } return 0; } /** * This method returns the number of logical pages which make up the report. This results of this method are available * only after validate/execute have been successfully called. This field has no setter, as it should never be set by * users. * * @return the number of logical pages in the report */ public int getPageCount() { return pageCount; } /** * Determines if the output type supports pagination or not. * * @return True if the output type supports pagination. */ public boolean outputSupportsPagination() { try { final String outputType = computeEffectiveOutputTarget(); final ReportOutputHandler reportOutputHandler = createOutputHandlerForOutputType( outputType ); if ( reportOutputHandler == null ) { log.warn( Messages.getInstance().getString( "ReportPlugin.warnUnprocessableRequest", outputType ) ); } else { return reportOutputHandler.supportsPagination(); } } catch ( Throwable t ) { log.error( Messages.getInstance().getString( "ReportPlugin.executionFailed" ), t ); //$NON-NLS-1$ } return false; } /** * This method will determine if the component instance 'is valid.' The validate() is called after all of the bean * 'setters' have been called, so we may validate on the actual values, not just the presence of inputs as we were * historically accustomed to. * <p/> * Since we should have a list of all action-sequence inputs, we can determine if we have sufficient inputs to meet * the parameter requirements of the report-definition. This would include validation of values and ranges of values. * * @return true if valid * @throws Exception */ public boolean validate() throws Exception { if ( reportDefinition == null && reportDefinitionInputStream == null && fileId == null && reportDefinitionPath == null ) { log.error( Messages.getInstance().getString( "ReportPlugin.reportDefinitionNotProvided" ) ); //$NON-NLS-1$ return false; } if ( outputStream == null && print == false ) { log.error( Messages.getInstance().getString( "ReportPlugin.outputStreamRequired" ) ); //$NON-NLS-1$ return false; } if ( inputs == null ) { log.error( Messages.getInstance().getString( "ReportPlugin.inputParameterRequired" ) ); //$NON-NLS-1$ return false; } return true; } /** * Perform the primary function of this component, this is, to execute. This method will be invoked immediately * following a successful validate(). * * @return true if successful execution * @throws Exception */ public boolean execute() throws Exception { final MasterReport report = getReport(); int yieldRate = getYieldRate(); if ( yieldRate > 0 ) { report.getReportConfiguration() .setConfigProperty( "org.pentaho.reporting.engine.classic.core.YieldRate", String.valueOf( yieldRate ) ); } try { final DefaultParameterContext parameterContext = new DefaultParameterContext( report ); // open parameter context final ValidationResult vr = applyInputsToReportParameters( parameterContext, null ); if ( vr.isEmpty() == false ) { return false; } parameterContext.close(); if ( isPrint() ) { // handle printing // basic logic here is: get the default printer, attempt to resolve the user specified printer, default back as // needed PrintService printService = PrintServiceLookup.lookupDefaultPrintService(); if ( StringUtils.isEmpty( getPrinter() ) == false ) { final PrintService[] services = PrintServiceLookup.lookupPrintServices( DocFlavor.SERVICE_FORMATTED.PAGEABLE, null ); for ( final PrintService service : services ) { if ( service.getName().equals( printer ) ) { printService = service; } } if ( ( printer == null ) && ( services.length > 0 ) ) { printService = services[ 0 ]; } } Java14PrintUtil.printDirectly( report, printService ); return true; } final String outputType = computeEffectiveOutputTarget(); final ReportOutputHandler reportOutputHandler = createOutputHandlerForOutputType( outputType ); if ( reportOutputHandler == null ) { log.warn( Messages.getInstance().getString( "ReportPlugin.warnUnprocessableRequest", outputType ) ); return false; } synchronized ( reportOutputHandler.getReportLock() ) { try { pageCount = reportOutputHandler.generate( report, acceptedPage, outputStream, getYieldRate() ); return pageCount != -1; } finally { reportOutputHandler.close(); } } } catch ( ReportDataFactoryException e ) { throw e; } catch ( ReportInterruptedException interrupt ) { log.info( "Report execution interrupted." ); } catch ( Exception e ) { log.error( Messages.getInstance().getString( "ReportPlugin.executionFailed" ), e ); //$NON-NLS-1$ } // lets not pretend we were successfull, if the export type was not a valid one. return false; } protected ReportOutputHandler createOutputHandlerForOutputType( final String outputType ) throws IOException { if ( inputs == null ) { throw new IllegalStateException( "Inputs are null, this component did not validate properly" ); } final Object attribute = report.getAttribute( AttributeNames.Pentaho.NAMESPACE, AttributeNames.Pentaho.REPORT_CACHE ); final ReportCacheKey reportCacheKey = new ReportCacheKey( getViewerSessionId(), inputs ); ReportCache cache; if ( Boolean.FALSE.equals( attribute ) ) { cache = new NullReportCache(); } else { cache = PentahoSystem.get( ReportCache.class ); final ReportOutputHandler outputHandler = cache.get( reportCacheKey ); if ( outputHandler != null ) { return outputHandler; } } if ( dashboardMode ) { report.getReportConfiguration().setConfigProperty( HtmlTableModule.BODY_FRAGMENT, "true" ); } ReportOutputHandlerFactory handlerFactory = PentahoSystem.get( ReportOutputHandlerFactory.class ); if ( handlerFactory == null ) { handlerFactory = new FastExportReportOutputHandlerFactory(); } ReportOutputHandler reportOutputHandler = handlerFactory.createOutputHandlerForOutputType( new InternalOutputHandlerSelector( outputType ) ); if ( reportOutputHandler == null ) { return null; } return cache.put( reportCacheKey, reportOutputHandler ); } /** * Perform a pagination run. * * @return the number of pages or streams generated. * @throws IOException if an IO error occurred while loading the report. * @throws ResourceException if a resource loading error occurred. */ public int paginate() throws IOException, ResourceException { final MasterReport report = getReport(); try { final ParameterContext parameterContext = new DefaultParameterContext( report ); // open parameter context final ValidationResult vr = applyInputsToReportParameters( parameterContext, null ); if ( vr.isEmpty() == false ) { return 0; } parameterContext.close(); if ( isPrint() ) { return 0; } final String outputType = computeEffectiveOutputTarget(); final ReportOutputHandler reportOutputHandler = createOutputHandlerForOutputType( outputType ); if ( reportOutputHandler == null ) { log.warn( Messages.getInstance().getString( "ReportPlugin.warnUnprocessableRequest", outputType ) ); return 0; } synchronized ( reportOutputHandler.getReportLock() ) { try { return reportOutputHandler.paginate( report, getYieldRate() ); } finally { reportOutputHandler.close(); } } } catch ( Throwable t ) { log.error( Messages.getInstance().getString( "ReportPlugin.executionFailed" ), t ); //$NON-NLS-1$ } // lets not pretend we were successfull, if the export type was not a valid one. return 0; } protected String getViewerSessionId() { if ( inputs == null ) { return null; } final Object o = inputs.get( ParameterXmlContentHandler.SYS_PARAM_SESSION_ID ); if ( o instanceof String ) { return String.valueOf( o ); } return null; } private class InternalOutputHandlerSelector implements ReportOutputHandlerSelector { private String outputType; private InternalOutputHandlerSelector( final String outputType ) { this.outputType = outputType; } public String getOutputType() { return outputType; } public MasterReport getReport() { return report; } public boolean isUseJcrOutput() { return false; } public String getJcrOutputPath() { return null; } public <T> T getInput( final String parameterName, final T defaultValue, final Class<T> idx ) { Object input = SimpleReportingComponent.this.getInput( parameterName, defaultValue ); if ( input == null ) { input = defaultValue; } return idx.cast( input ); } } protected int checkAndGetUserInputQueryLimit( final Object userInputQueryLimit, final int reportQueryLimit ) { int systemQueryLimit = 0; final IPluginManager pm = PentahoSystem.get( IPluginManager.class ); if ( pm != null ) { final boolean isQueryLimitControlEnabled = Boolean.parseBoolean( (String) pm.getPluginSetting( "reporting", "settings/query-limit-ui-enabled", "false" ) ); systemQueryLimit = isQueryLimitControlEnabled ? NumberUtils.toInt( (String) pm.getPluginSetting( "reporting", "settings/query-limit", "0" ), 0 ) : 0; } if ( reportQueryLimit > 0 ) { //Report limit is set - we should return either report limit or system if ( reportQueryLimit > 0 && systemQueryLimit > 0 ) { //System limit is set - return min (reportLimit, systemLimit) return Math.min( reportQueryLimit, systemQueryLimit ); } else { //System limit is not set - return report limit return reportQueryLimit; } } else { //Report limit is not set - we should return either user limit or system final int userLimit = NumberUtils.toInt( (String) userInputQueryLimit, -1 ); if ( userLimit > 0 && systemQueryLimit > 0 ) { //System limit and user limit are set - return min (reportLimit, systemLimit) return Math.min( userLimit, systemQueryLimit ); } else if ( systemQueryLimit > 0 ) { //System limit is set but no user limit - return system limit return systemQueryLimit; } else { //System limit is not set - return user limit or -1 by default return userLimit; } } } }